package away3d.core.partition ;


import away3d.cameras.Camera3D ;
import away3d.core.math.Plane3D ;
import away3d.entities.Entity ;
import away3d.primitives.WireframeCube ;
import away3d.primitives.WireframePrimitiveBase ;



class OctreeNode extends NodeBase
{
    
    
    private var _centerX            : Float ;
    private var _centerY            : Float ;
    private var _centerZ            : Float ;
    private var _minX               : Float ;
    private var _minY               : Float ;
    private var _minZ               : Float ;
    private var _maxX               : Float ;
    private var _maxY               : Float ;
    private var _maxZ               : Float ;
    private var _quadSize           : Float ;
    private var _depth              : Float ;
    private var _leaf               : Bool ;
    
    private var _rightTopFar        : OctreeNode ;
    private var _leftTopFar         : OctreeNode ;
    private var _rightBottomFar     : OctreeNode ;
    private var _leftBottomFar      : OctreeNode ;
    private var _rightTopNear       : OctreeNode ;
    private var _leftTopNear        : OctreeNode ;
    private var _rightBottomNear    : OctreeNode ;
    private var _leftBottomNear     : OctreeNode ;

    private var _entityWorldBounds  : Vector<Float> ;
    private var _halfExtent         : Float ;
    
    
    
    public function new (   maxDepth    : Int   = 5
                        ,   size        : Float = 10000
                        ,   centerX     : Float = 0
                        ,   centerY     : Float = 0
                        ,   centerZ     : Float = 0
                        ,   depth       : Int   = 0 
                        )
    {
        
        _entityWorldBounds = new Vector() ;
        init( size, centerX, centerY, centerZ, depth, maxDepth ) ;
        
    }
    
    
    private function init( size : Float, centerX : Float, centerY : Float, centerZ : Float, depth : int, maxDepth : int )
    {
        
        _halfExtent = size * .5 ;
        _centerX    = centerX ;
        _centerY    = centerY ;
        _centerZ    = centerZ ;
        _quadSize   = size ;
        _depth      = depth ;
        _minX       = centerX - _halfExtent ;
        _minY       = centerY - _halfExtent ;
        _minZ       = centerZ - _halfExtent ;
        _maxX       = centerX + _halfExtent ;
        _maxY       = centerY + _halfExtent ;
        _maxZ       = centerZ + _halfExtent ;
        
        _leaf       = depth == maxDepth ;
        
        if( !_leaf )
        {
            
            var hhs = _halfExtent * .5 ;
            addNode( _leftTopNear       = new OctreeNode( maxDepth, _halfExtent, centerX - hhs, centerY + hhs, centerZ - hhs, depth + 1 ) ) ;
            addNode( _rightTopNear      = new OctreeNode( maxDepth, _halfExtent, centerX + hhs, centerY + hhs, centerZ - hhs, depth + 1 ) ) ;
            addNode( _leftBottomNear    = new OctreeNode( maxDepth, _halfExtent, centerX - hhs, centerY - hhs, centerZ - hhs, depth + 1 ) ) ;
            addNode( _rightBottomNear   = new OctreeNode( maxDepth, _halfExtent, centerX + hhs, centerY - hhs, centerZ - hhs, depth + 1 ) ) ;
            addNode( _leftTopFar        = new OctreeNode( maxDepth, _halfExtent, centerX - hhs, centerY + hhs, centerZ + hhs, depth + 1 ) ) ;
            addNode( _rightTopFar       = new OctreeNode( maxDepth, _halfExtent, centerX + hhs, centerY + hhs, centerZ + hhs, depth + 1 ) ) ;
            addNode( _leftBottomFar     = new OctreeNode( maxDepth, _halfExtent, centerX - hhs, centerY - hhs, centerZ + hhs, depth + 1 ) ) ;
            addNode( _rightBottomFar    = new OctreeNode( maxDepth, _halfExtent, centerX + hhs, centerY - hhs, centerZ + hhs, depth + 1 ) ) ;
            
        }
        
    }
    
    
    override private function createDebugBounds() : WireframePrimitiveBase
    {
        
        var cube    = new WireframeCube( _quadSize, _quadSize, _quadSize ) ;
        cube.x      = _centerX ;
        cube.y      = _centerY ;
        cube.z      = _centerZ ;
        
        return cube ;
        
    }
    
    
    override public function isInFrustum( camera : Camera3D ) : Bool
    {
        
        var a       : Float ;
        var b       : Float ;
        var c       : Float ;
        var dd      : Float ;
        var rr      : Float ;
        var plane   : Plane3D ;
        var frustum = camera.frustumPlanes ;
        
        for( i in 0...6 )
        {
            
            plane   = frustum[ i ] ;
            a       = plane.a ;
            b       = plane.b ;
            c       = plane.c ;
            dd      = a * _centerX + b * _centerY + c * _centerZ ;
            
            if( a < 0 ) a = -a ;
            if( b < 0 ) b = -b ;
            if( c < 0 ) c = -c ;
            
            rr      = _halfExtent * ( a + b + c ) ;
            
            if( dd + rr < -plane.d ) return false ;
            
        }
        
        return true ;
        
    }
    
    
    override public function findPartitionForEntity( entity : Entity ) : NodeBase
    {
        
        entity.sceneTransform.transformVectors( entity.bounds.aabbPoints, _entityWorldBounds ) ;
        
        return findPartitionForBounds( _entityWorldBounds ) ;
        
    }
    
    
    private function findPartitionForBounds( entityWorldBounds : Vector<Float> ) : OctreeNode
    {
        
        var i       : Int  ;
        var x       : Float ;
        var y       : Float ;
        var z       : Float ;
        var left    : Bool ;
        var right   : Bool ;
        var far     : Bool ;
        var near    : Bool ;
        var top     : Bool ;
        var bottom  : Bool ;
        
        if( _leaf )     return this ;
        
        while( i < 24 )
        {
            
            x = entityWorldBounds[ i++ ] ;
            y = entityWorldBounds[ i++ ] ;
            z = entityWorldBounds[ i++ ] ;
            
            if( x > _centerX ) 
            {
                
                if( left ) return this ;
                right = true ;
                
            }
            else
            {
                
                if( right ) return this ;
                left = true ;
                
            }
            
            if( y > _centerY )
            {
                
                if( bottom ) return this ;
                top = true ;
                
            }
            else
            {
                
                if( top ) return this ;
                bottom = true ;
                
            }
            
            if( z > _centerZ )
            {
                
                if( near ) return this ;
                far = true ;
                
            }
            else
            {
                
                if( far ) return this ;
                near = true ;
                
            }
            
        }
        
        if( top )
        {
            
            if( near )
            {
                
                if( left ) return _leftTopNear.findPartitionForBounds( entityWorldBounds ) ;
                else return _rightTopNear.findPartitionForBounds( entityWorldBounds ) ;
                
            }
            else
            {
                
                if( left ) return _leftTopFar.findPartitionForBounds( entityWorldBounds ) ;
                else return _rightTopFar.findPartitionForBounds( entityWorldBounds ) ;
                
            }
            
        }
        else
        {
            
            if( near )
            {
                
                if( left ) return _leftBottomNear.findPartitionForBounds( entityWorldBounds ) ;
                else return _rightBottomNear.findPartitionForBounds( entityWorldBounds ) ;
                
            }
            else
            {
                
                if( left ) return _leftBottomFar.findPartitionForBounds( entityWorldBounds ) ;
                else return _rightBottomFar.findPartitionForBounds( entityWorldBounds ) ;
                
            }
            
        }
        
    }
    
    
}